package cn.ydxiaoshuai.pdf.diyUtils;


import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.ydxiaoshuai.pdf.PDFToImageUtil;
import org.apache.fontbox.cff.CFFCIDFont;
import org.apache.fontbox.cff.CFFFont;
import org.apache.fontbox.ttf.OpenTypeFont;
import org.apache.fontbox.ttf.TrueTypeCollection;
import org.apache.fontbox.ttf.TrueTypeFont;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.pdmodel.font.*;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.util.Charsets;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * pdf工具类
 *
 * @author xwt
 * @date 2022/12/7 11:00
 */
public class PDFUtils {

    private static Map<String, FontInfo> fontInfoMap = null;

    private static final String TTC_TYPE = "TTC";

    private static final String OTC_TYPE = "OTC";

    /***
     * pdfBase64转图片，返回base64编码集合
     *
     * @param pdfBase64  pdf类型base64
     * @return 图片base64
     */
    public static List<String> pdfToImageBase64(String pdfBase64) {
        List<String> list = new ArrayList<>();
        if (ObjectUtil.isEmpty(pdfBase64)) {
            return list;
        }
        return PDFToImageUtil.imageToBase64(PDFUtils.pdfToImage(pdfBase64));
    }

    /***
     * pdfBase64转图片，返回图片对象
     *
     * @param pdfBase64  pdf类型base64
     * @return BufferedImage
     */
    public static List<BufferedImage> pdfToImage(String pdfBase64) {
        List<BufferedImage> list = new ArrayList<>();
        if (ObjectUtil.isEmpty(pdfBase64)) {
            return list;
        }
        byte[] decode = Base64Decoder.decode(pdfBase64.getBytes(StandardCharsets.UTF_8));
        try (
                ByteArrayInputStream stream = new ByteArrayInputStream(decode);
                // 加载解析PDF文件
                PDDocument doc = PDDocument.load(stream);
        ) {
            // 业务处理
            PDFRenderer pdfRenderer = new PDFRenderer(doc);
            PDPageTree pages = doc.getPages();
            int pageCount = pages.getCount();
            for (int i = 0; i < pageCount; i++) {
                list.add(pdfRenderer.renderImageWithDPI(i, 300));
            }
        } catch (Exception e) {
            System.err.println("pdfBase64转图片异常" + e.getMessage());
        }
        return list;
    }

    /***
     * 设置字体
     * @param fontFormat 字体文件类型
     * @param fontName 字体名称
     * @param file 字体文件
     */
    public static void setFonts(FontFormat fontFormat, String fontName, File file) {
        FontInfo fontInfo = getFontInfoMap().get(fontName);
        if (fontInfo != null) {
            System.err.println("pdfFont添加字体已经存在");
            return;
        }
        // 后缀
        System.err.println("pdfFont 添加字体" + file.getName());
        String suffix = FileUtil.getSuffix(file);
        if (TTC_TYPE.equalsIgnoreCase(suffix) || OTC_TYPE.equalsIgnoreCase(suffix)) {
            try (TrueTypeCollection trueTypeCollection = new TrueTypeCollection(file);) {
                trueTypeCollection.processAllFonts(trueTypeFont -> addTrueTypeFontImpl(trueTypeFont, file));
            } catch (IOException e) {
                System.err.println("无法加载字体" + file.getAbsolutePath());
            }
            return;
        }
        getFontInfoMap().put(fontName, new PDFFontInfo(file, fontFormat, fontName));
    }

    /***
     * 设置字体,扫描目录下所有字体
     * @param dir 目录
     */
    public static void setFonts(File dir) {
        if (dir == null || !dir.exists() || !dir.isDirectory()) {
            System.err.println("pdfFont添加字体为空");
            return;
        }
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                setFonts(file);
            } else {
                String fileName = file.getName();
                // 后缀
                String suffix = FileUtil.getSuffix(fileName);
                // 文件名称，不带后缀
                String prefix = FileUtil.getPrefix(fileName);
                if (FontFormat.OTF.name().equalsIgnoreCase(suffix)) {
                    setFonts(FontFormat.OTF, prefix, file);
                } else if (FontFormat.TTF.name().equalsIgnoreCase(suffix) || TTC_TYPE.equalsIgnoreCase(suffix) || OTC_TYPE.equalsIgnoreCase(suffix)) {
                    setFonts(FontFormat.TTF, prefix, file);
                } else if (FontFormat.PFB.name().equalsIgnoreCase(suffix)) {
                    setFonts(FontFormat.PFB, prefix, file);
                } else {
                    System.err.println("无法识别字体：" + file.getAbsolutePath());
                }
            }
        }
    }

    /***
     * 获取系统字体缓存
     * @return 字体缓存
     */
    private static Map<String, FontInfo> getFontInfoMap() {
        if (fontInfoMap != null) {
            return fontInfoMap;
        }
        FontMapper instance = FontMappers.instance();
        // 初始化加载系统字体
        instance.getCIDFont("STSong-Light", null, null);
        Class<? extends FontMapper> aClass = instance.getClass();
        try {
            Field field = aClass.getDeclaredField("fontInfoByName");
            if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {
                field.setAccessible(true);
            }
            // 获取系统字体缓存
            fontInfoMap = (Map<String, FontInfo>) field.get(instance);
        } catch (Exception e) {
            throw new RuntimeException("PdfUtils初始化异常");
        }
        return fontInfoMap;
    }

    /***
     * 将OTF或TTF字体添加到文件缓存
     * @param ttf 字体
     * @param file 字体文件
     * @throws IOException 异常
     */
    private static void addTrueTypeFontImpl(TrueTypeFont ttf, File file) throws IOException {
        if (ttf.getName() != null) {
            if (ttf.getHeader() == null) {
                getFontInfoMap().put(ttf.getName(), new PDFFontInfo(file, FontFormat.TTF, ttf.getName()));
                return;
            }
            int macStyle = ttf.getHeader().getMacStyle();

            int sFamilyClass = -1;
            int usWeightClass = -1;
            int ulCodePageRange1 = 0;
            int ulCodePageRange2 = 0;
            // Apple's AAT fonts don't have an OS/2 table
            if (ttf.getOS2Windows() != null) {
                sFamilyClass = ttf.getOS2Windows().getFamilyClass();
                usWeightClass = ttf.getOS2Windows().getWeightClass();
                ulCodePageRange1 = (int) ttf.getOS2Windows().getCodePageRange1();
                ulCodePageRange2 = (int) ttf.getOS2Windows().getCodePageRange2();
            }

            CIDSystemInfo ros = null;
            String registry = null;
            String ordering = null;
            int supplement = 0;
            FontFormat fontFormat;
            if (ttf instanceof OpenTypeFont && ((OpenTypeFont) ttf).isPostScript()) {
                CFFFont cff = ((OpenTypeFont) ttf).getCFF().getFont();
                fontFormat = FontFormat.OTF;
                if (cff instanceof CFFCIDFont) {
                    CFFCIDFont cidFont = (CFFCIDFont) cff;
                    registry = cidFont.getRegistry();
                    ordering = cidFont.getOrdering();
                    supplement = cidFont.getSupplement();
                }
            } else {
                fontFormat = FontFormat.TTF;
                if (ttf.getTableMap().containsKey("gcid")) {
                    // Apple's AAT fonts have a "gcid" table with CID info
                    byte[] bytes = ttf.getTableBytes(ttf.getTableMap().get("gcid"));
                    String reg = new String(bytes, 10, 64, Charsets.US_ASCII);
                    registry = reg.substring(0, reg.indexOf('\0'));
                    String ord = new String(bytes, 76, 64, Charsets.US_ASCII);
                    ordering = ord.substring(0, ord.indexOf('\0'));
                    supplement = bytes[140] << 8 & bytes[141];
                }
            }
            try {
                Constructor<CIDSystemInfo> constructor = CIDSystemInfo.class.getDeclaredConstructor(String.class, String.class, int.class);
                if ((!Modifier.isPublic(constructor.getModifiers()) || !Modifier.isPublic(constructor.getDeclaringClass().getModifiers()) || Modifier.isFinal(constructor.getModifiers())) && !constructor.isAccessible()) {
                    constructor.setAccessible(true);
                }
                ros = constructor.newInstance(registry, ordering, supplement);
            } catch (Exception e) {
                e.printStackTrace();
            }
            getFontInfoMap().put(ttf.getName(), new PDFFontInfo(file, fontFormat, ttf.getName(), ros, usWeightClass, sFamilyClass, ulCodePageRange1, ulCodePageRange2,
                    macStyle));
        }
    }
}
